﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Collections.Generic;

using Nova.Parsing;
using Nova.Rendering;

namespace Nova.CodeDOM
{
    /// <summary>
    /// The common base class of all <see cref="Statement"/>s that can have a <see cref="Block"/> as a body (<see cref="NamespaceDecl"/>,
    /// <see cref="TypeDecl"/>, <see cref="MethodDeclBase"/>, <see cref="PropertyDeclBase"/>, <see cref="BlockDecl"/>, <see cref="IfBase"/>,
    /// <see cref="Else"/>, <see cref="Switch"/>, <see cref="SwitchItem"/>, <see cref="For"/>, <see cref="ForEach"/>, <see cref="While"/>,
    /// <see cref="Try"/>, <see cref="Catch"/>, <see cref="Finally"/>, <see cref="Using"/>, <see cref="Lock"/>, <see cref="CheckedBlock"/>,
    /// <see cref="UncheckedBlock"/>).
    /// </summary>
    public abstract class BlockStatement : Statement, IBlock
    {
        #region /* FIELDS */

        /// <summary>
        /// The body is always a Block, which in turn may contain zero or more other code objects,
        /// and it can also be null in special cases (such as for method signatures with no body,
        /// delegate declarations, or a While with the semi-colon on the same line).
        /// </summary>
        protected Block _body;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a <see cref="BlockStatement"/>.
        /// </summary>
        protected BlockStatement()
        {
            Body = new Block();
        }

        /// <summary>
        /// Create a <see cref="BlockStatement"/> with the specified <see cref="CodeObject"/> in the body.
        /// </summary>
        protected BlockStatement(CodeObject body, bool allowNullBody)
        {
            // Allow derived classes to pass any non-Block code object, in which case it will
            // be wrapped in a Block.
            Body = (body == null ? (allowNullBody ? null : new Block()) : (body is Block ? (Block)body : new Block(body)));
        }

        /// <summary>
        /// Create a <see cref="BlockStatement"/> from an array of <see cref="CodeObject"/>s.
        /// </summary>
        protected BlockStatement(params CodeObject[] objects)
        {
            Add(objects);
        }

        /// <summary>
        /// Create a <see cref="BlockStatement"/> from an existing one, moving the body.
        /// </summary>
        protected BlockStatement(BlockStatement blockStatement)
            : base(blockStatement)
        {
            _body = blockStatement.Body;  // bypass body formatting
            blockStatement.Body = null;
            _body.Parent = this;
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The <see cref="Block"/> body.
        /// </summary>
        public Block Body
        {
            get { return _body; }
            set
            {
                _body = value;
                if (_body != null)
                {
                    _body.Parent = this;
                    ReformatBlock();
                }
                HasTerminator = HasTerminatorDefault;
            }
        }

        /// <summary>
        /// True for all <see cref="BlockStatement"/>s that have a header (all except <see cref="CodeUnit"/> and <see cref="BlockDecl"/>).
        /// </summary>
        public virtual bool HasHeader
        {
            get { return true; }
        }

        /// <summary>
        /// True if a <see cref="BlockStatement"/> is at the top level (those that have no header and no indent).
        /// For example, a <see cref="CodeUnit"/>, a <see cref="BlockDecl"/> with no parent or a <see cref="DocComment"/>
        /// parent.
        /// </summary>
        public virtual bool IsTopLevel
        {
            get { return (!HasHeader && (_parent == null || _parent is DocComment)); }
        }

        /// <summary>
        /// True for multi-part statements, such as try/catch/finally or if/else.
        /// </summary>
        public virtual bool IsMultiPart
        {
            get { return false; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Create a body if one doesn't exist yet.
        /// </summary>
        public Block CreateBody()
        {
            if (_body == null)
                Body = new Block();
            return _body;
        }

        /// <summary>
        /// Add a <see cref="CodeObject"/> to the <see cref="BlockStatement"/> body.
        /// </summary>
        public virtual void Add(CodeObject obj)
        {
            CreateBody().Add(obj);
        }

        /// <summary>
        /// Add multiple <see cref="CodeObject"/>s to the <see cref="BlockStatement"/> body.
        /// </summary>
        public virtual void Add(params CodeObject[] objects)
        {
            CreateBody();
            foreach (CodeObject obj in objects)
                _body.Add(obj);
        }

        /// <summary>
        /// Add a collection of <see cref="CodeObject"/>s to the <see cref="BlockStatement"/> body.
        /// </summary>
        /// <param name="collection">The collection to be added.</param>
        public virtual void AddRange(IEnumerable<CodeObject> collection)
        {
            CreateBody().AddRange(collection);
        }

        /// <summary>
        /// Insert a <see cref="CodeObject"/> at the specified index in the <see cref="BlockStatement"/> body.
        /// </summary>
        /// <param name="index">The index at which to insert.</param>
        /// <param name="obj">The CodeObject to be inserted.</param>
        public virtual void Insert(int index, CodeObject obj)
        {
            CreateBody().Insert(index, obj);
        }

        /// <summary>
        /// Remove the specified <see cref="CodeObject"/> from the <see cref="BlockStatement"/> body.
        /// </summary>
        public virtual void Remove(CodeObject obj)
        {
            if (_body != null)
                _body.Remove(obj);
        }

        /// <summary>
        /// Remove the <see cref="CodeObject"/> at the specified index from the <see cref="BlockStatement"/>.
        /// </summary>
        public void RemoveAt(int index)
        {
            if (_body != null)
                _body.RemoveAt(index);
        }

        /// <summary>
        /// Remove all <see cref="CodeObject"/>s from the <see cref="BlockStatement"/> body.
        /// </summary>
        public virtual void RemoveAll()
        {
            if (_body != null)
                _body.RemoveAll();
        }

        /// <summary>
        /// Replace the specified <see cref="CodeObject"/> with a new one.
        /// </summary>
        /// <returns>True if the code object was found and replaced, otherwise false.</returns>
        public bool Replace(CodeObject oldObject, CodeObject newObject)
        {
            return (_body != null && _body.Replace(oldObject, newObject));
        }

        /// <summary>
        /// Deep-clone the code object.
        /// </summary>
        public override CodeObject Clone()
        {
            BlockStatement clone = (BlockStatement)base.Clone();
            clone.CloneField(ref clone._body, _body);
            return clone;
        }

        /// <summary>
        /// Check if the <see cref="BlockStatement"/> contains the specified <see cref="CodeObject"/>.
        /// </summary>
        /// <param name="codeObject">The object being searched for.</param>
        /// <returns>True if the block contains the object, otherwise false.</returns>
        public bool Contains(CodeObject codeObject)
        {
            return (_body != null && _body.Contains(codeObject));
        }

        /// <summary>
        /// Enumerate all children with the specified name.
        /// </summary>
        public IEnumerable<CodeObject> Find(string name)
        {
            if (_body != null)
            {
                foreach (CodeObject codeObject in _body.Find(name))
                    yield return codeObject;
            }
        }

        /// <summary>
        /// Enumerate all children with the specified name and type.
        /// </summary>
        public IEnumerable<T> Find<T>(string name) where T : CodeObject
        {
            if (_body != null)
            {
                foreach (T codeObject in _body.Find<T>(name))
                    yield return codeObject;
            }
        }

        /// <summary>
        /// Enumerate all children of type T.
        /// </summary>
        public IEnumerable<T> Find<T>() where T : CodeObject
        {
            if (_body != null)
            {
                foreach (T codeObject in _body.Find<T>())
                    yield return codeObject;
            }
        }

        /// <summary>
        /// Find the first child object with the specified name and type.
        /// </summary>
        public T FindFirst<T>(string name) where T : CodeObject
        {
            return (_body != null ? _body.FindFirst<T>(name) : null);
        }

        /// <summary>
        /// Find the first child object of type T.
        /// </summary>
        public T FindFirst<T>() where T : CodeObject
        {
            return (_body != null ? _body.FindFirst<T>() : null);
        }

        /// <summary>
        /// Find the index of the specified <see cref="CodeObject"/> in the <see cref="BlockStatement"/>.
        /// </summary>
        /// <param name="codeObject">The object being searched for.</param>
        /// <returns>The index of the code object, or -1 if not found.</returns>
        public int FindIndexOf(CodeObject codeObject)
        {
            return (_body != null ? _body.FindIndexOf(codeObject) : -1);
        }

        #endregion

        #region /* PARSING */

        protected BlockStatement(Parser parser, CodeObject parent)
            : base(parser, parent)
        { }

        protected void ParseKeywordArgumentBody(Parser parser, ref Expression argument, bool allowNullBody, bool noPostProcessing)
        {
            ParseKeywordAndArgument(parser, ref argument);  // Parse the keyword and argument

            if (allowNullBody && parser.TokenText == Terminator && !parser.Token.IsFirstOnLine)
                ParseTerminator(parser);  // Handle same-line ';' (null body)
            else
                new Block(out _body, parser, this, false);  // Parse the body
        }

        protected void ParseTerminatorOrBody(Parser parser, ParseFlags flags)
        {
            // Check for an optional ';' in place of the body
            if (parser.TokenText == Terminator)
            {
                ParseTerminator(parser);

                // Check for compiler directives, storing them as postfix annotations on the parent
                Block.ParseCompilerDirectives(parser, this, AnnotationFlags.IsPostfix, false);
            }
            else
            {
                if (flags.HasFlag(ParseFlags.SkipMethodBodies))
                    Block.SkipParsingBlock(parser, this, true);
                else
                    new Block(out _body, parser, this, true);  // Parse the body
            }
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the <see cref="Statement"/> has an argument.
        /// </summary>
        public override bool HasArgument
        {
            get { return false; }
        }

        /// <summary>
        /// Reformat the <see cref="Block"/> body.
        /// </summary>
        public virtual void ReformatBlock()
        {
            if (_body != null)
            {
                if (!_body.IsGroupingSet)
                    _body.SetFormatFlag(FormatFlags.Grouping, ShouldHaveBraces());
                if (!IsNewLinesSet)
                {
                    IsSingleLine = (IsSingleLineDefault && _body.IsSingleLineDefault && _body.Count < 2);
                    if (_body.Count == 0 && IsCompactIfEmptyDefault)
                        _body.SetNewLines(0);
                }
            }
        }

        /// <summary>
        /// True if the code object only requires a single line for display by default.
        /// </summary>
        public override bool IsSingleLineDefault
        {
            get { return false; }
        }

        /// <summary>
        /// Determines if the code object only requires a single line for display.
        /// </summary>
        public override bool IsSingleLine
        {
            get { return (base.IsSingleLine && (_body == null || (!_body.IsFirstOnLine && _body.IsSingleLine))); }
            set
            {
                // Make sure there's a body, and set its IsFirstOnLine and IsSingleLine properties appropriately
                CreateBody();
                _body.IsFirstOnLine = !value;
                _body.IsSingleLine = value;
            }
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> always requires braces.
        /// </summary>
        public virtual bool HasBracesAlways
        {
            get { return true; }
        }

        /// <summary>
        /// Determines if the body of the <see cref="BlockStatement"/> should be formatted with braces.
        /// </summary>
        public virtual bool ShouldHaveBraces()
        {
            // Check if braces aren't optional for the statement
            if (HasBracesAlways)
                return true;
            // No braces are required if we have no body, or it's empty
            if (_body == null || _body.Count == 0)
                return false;
            // Braces are required (by default) if we have multiple objects in the block
            // (this behavior is overridden by SwitchItem, where multiple objects are legal without braces).
            if (_body.Count > 1)
                return true;
            // We only have a single child statement - use braces if it's not single-line
            return !_body[0].IsSingleLine;
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> has braces.
        /// </summary>
        public bool HasBraces
        {
            get { return (_body != null && _body.HasBraces); }
            set
            {
                if (HasBracesAlways && !value)
                    throw new Exception("Braces can't be turned off for the given type of block statement!");
                CreateBody().HasBraces = value;
            }
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> has compact empty braces by default.
        /// </summary>
        public virtual bool IsCompactIfEmptyDefault
        {
            get { return false; }
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> requires an empty statement if it has an empty block with no braces.
        /// </summary>
        public virtual bool RequiresEmptyStatement
        {
            get { return true; }
        }

        /// <summary>
        /// True if the <see cref="Statement"/> has a terminator character by default.
        /// </summary>
        public override bool HasTerminatorDefault
        {
            get { return (_body == null); }
        }

        /// <summary>
        /// Default format the code object.
        /// </summary>
        protected internal override void DefaultFormat()
        {
            base.DefaultFormat();

            // Default the braces if they haven't been explicitly set
            if (!IsGroupingSet)
                _formatFlags = ((_formatFlags & ~FormatFlags.Grouping) | (ShouldHaveBraces() ? FormatFlags.Grouping : 0));
        }

        #endregion

        #region /* RENDERING */

        protected override void AsTextAfter(CodeWriter writer, RenderFlags flags)
        {
            base.AsTextAfter(writer, flags);
            if (_body != null && !flags.HasFlag(RenderFlags.Description))
                _body.AsText(writer, flags);
        }

        #endregion
    }
}
